﻿// -------------------------------------------------------------------------------------------------------
// LICENSE INFORMATION
//
// - This software is licensed under the MIT shared source license.
// - The "official" source code for this project is maintained at http://mtcagent.codeplex.com
//
// Copyright (c) 2010 OpenNETCF Consulting
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
// associated documentation files (the "Software"), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial 
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
// -------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.IO;
using System.Reflection;
using OpenNETCF.IoC;
using System.Diagnostics;
using System.Xml.Linq;
using System.Threading;

namespace OpenNETCF.MTConnect
{
    internal class HostedAdapterService : IHostedAdapterService
    {
        [EventPublication(EventNames.StatusUpdate)]
        public event EventHandler<GenericEventArgs<StatusSummary>> StatusUpdateRequested;

        public string AdapterFolder { get; private set; }
        public string NewAdapterFolder { get; private set; }
        private IMTConnectProvider Provider { get; set; }

        private List<IHostedAdapter> m_adapters = new List<IHostedAdapter>();
        private IAdapterWatcher m_watcher;
        private IAgentInterface m_localInterface;
        private Dictionary<string, Type> m_loadedDlls = new Dictionary<string, Type>();

        private string[] m_systemDLLs = new string[] {  "mscorlib", 
                                                        "System", 
                                                        "System.Xml", 
                                                        "OpenNETCF",
                                                        "MTC.ControllerSupport",
                                                        "OpenNETCF.Drawing",
                                                        "OpenNETCF.Windows.Forms"/*,
                                                        "MTC.AdaptersSupport.CF",
                                                        "MTC.Builder.Common.CF",
                                                        "MTC.Controllers.Common.CF",
                                                        "OpenNETCF.Configuration",
                                                        "OpenNETCF.Extensions"*/
                                                        };

        [InjectionConstructor]
        public HostedAdapterService([ServiceDependency]IMTConnectProvider provider, [ServiceDependency]IConfigurationService configService)
        {
            if (provider == null)
            {
                Debug.WriteLine("HostedAdapterService ctor: provider is null");
            }
            else
            {
                Provider = provider;
                provider.Agent.Adapters.Cleared += new EventHandler(Adapters_Cleared);
                provider.AdapterConfigurationChanged += new EventHandler(ProviderAdapterConfigurationChanged);
            }

            if (configService == null)
            {
                Debug.WriteLine("HostedAdapterService ctor: configService is null");
            }
            else
            {
                var config = configService.AgentConfiguration;

                // see if adapter folders exist
                var path = Path.GetFullPath(config.NewHostedAdapterFolder);
                var di = new DirectoryInfo(path);
                if (!di.Exists)
                {
                    // if not, create it
                    di.Create();
                }
                NewAdapterFolder = di.FullName;

                path = Path.GetFullPath(config.HostedAdapterFolder);
                di = new DirectoryInfo(path);
                if (!di.Exists)
                {
                    // if not, create it
                    di.Create();
                }
                AdapterFolder = di.FullName;

                UpdateAdapters();

                // add a watcher for file adds
                m_watcher = AdapterWatcherFactory.GetWatcher(AdapterFolder);
                m_watcher.NewDLLDetected += new EventHandler<GenericEventArgs<string>>(m_watcher_NewDLLDetected);

                // look for DLLs
                var dlls = di.GetFiles("*.dll");

                foreach (var dll in dlls)
                {
                    LoadDLL(dll.FullName);
                }
            }
        }

        // this is called after all DI Modules are loaded
        internal void InitializeAdapters()
        {
            ThreadPool.QueueUserWorkItem(delegate
            {
                // Thread.Sleep(1000);
                lock (m_adapters)
                {
                    foreach (var adapter in m_adapters)
                    {
                        try
                        {
                            adapter.AgentInitialized();
                        }
                        catch (Exception ex)
                        {
                            adapter.OnError(ex);
                        }
                    }
                }
            });
        }

        public void Shutdown()
        {
            lock (m_adapters)
            {
                for (int i = m_adapters.Count - 1; i >= 0; i--)
                {
                    m_adapters[i].Dispose();
                    m_adapters[i] = null;
                    m_adapters.RemoveAt(i);
                }
            }
        }

        private void UpdateAdapters()
        {
            if (NewAdapterFolder.IsNullOrEmpty()) return;

            foreach (var file in Directory.GetFiles(NewAdapterFolder))
            {
                var src = new FileInfo(file);
                var dst = new FileInfo(Path.Combine(AdapterFolder, src.Name));

                if(dst.Exists) dst.Delete();
                src.MoveTo(dst.FullName);
            }
        }

        public IHostedAdapter[] LoadedAdapters
        {
            get { return m_adapters.ToArray(); }
        }

        public XElement GetAdapterMethodInfos(IHostedAdapter adapter)
        {
            if (adapter == null) return null;
            return adapter.GetMethodInfos();
        }

        public string CallAdapterMethod(string adapterID, string methodName, string[] parameters)
        {
            var targetAdapter = (from a in m_adapters
                                where string.Compare(a.ID, adapterID, true) == 0
                                select a).FirstOrDefault();

            if(targetAdapter == null) throw new ArgumentException(string.Format( "Adapter ID '{0}' does not exist", adapterID));

            return targetAdapter.CallMethod(methodName, parameters);
        }

        private void LoadDLL(string filePath)
        {
            try
            {
                IInstanceService instanceService = RootWorkItem.Services.Get<IInstanceService>();
                if (m_systemDLLs.Contains(Path.GetFileNameWithoutExtension(filePath), new CaselessStringComparer())) return;

                var assembly = Assembly.LoadFrom(filePath);

                var adapterTypes = from a in assembly.GetTypes()
                                   //where a.GetInterfaces().FirstOrDefault(i=>i.Name == "IHostedAdapter") != null
                                   where a.GetInterfaces().Contains(typeof(IHostedAdapter))
                                   select a;

                foreach (var type in adapterTypes)
                {
                    if (type.IsAbstract) continue;

                    string adapterName = assembly.GetName().Name;
                    List<string> instanceNames = instanceService.GetAdapterInstanceNames(assembly.GetName().Name);

                    m_loadedDlls.Add(adapterName, type);

                    if (instanceNames != null && instanceNames.Count > 0)
                    {
                        foreach (var instanceName in instanceNames)
                        {
                            LoadInstance(type, instanceName, filePath);
                        }
                    }
                    else if (instanceNames == null)
                    {
                        string adapterString = "Adapter";
                        string instanceName = adapterName;
                        if (adapterName.EndsWith(adapterString))
                        {
                            instanceName = adapterName.Substring(0, adapterName.Length - adapterString.Length);
                            if (instanceName.Length == 0)
                            {
                                instanceName = adapterName;
                            }
                        }
                        LoadInstance(type, instanceName, filePath);
                    }
                }
            }
            catch (BadImageFormatException)
            {
                // ignore attempts to load non-managed DLLs
            }
            catch (Exception ex)
            {
                var msg = string.Format("Error loading HostedAdapter from {0}: {1}", Path.GetFileName(filePath), ex.Message);
                RaiseStatusUpdateRequested("[local]", msg);
                Debug.WriteLine(msg);
                if (Debugger.IsAttached) Debugger.Break();
                // TODO:
            }
        }

        internal IHostedAdapter LoadInstance(Type type, string name, string filePath)
        {
            try
            {
                var adapter = Activator.CreateInstance(type) as HostedAdapterBase;

                if (adapter == null)
                {
                    // TODO: log this
                    if (Debugger.IsAttached) Debugger.Break();
                    return null;
                }
                adapter.LogService = RootWorkItem.Services.Get<ILogService>();
                adapter.ID = name;
                InitializeAdapter(adapter);
                lock (m_adapters)
                {
                    m_adapters.Add(adapter);
                }
                var msg = string.Format("Loaded '{0}' HostedAdapter from {1}", name, Path.GetFileName(filePath));
                RaiseStatusUpdateRequested("[local]", msg);
                Debug.WriteLine(msg);
                return adapter;
            }

            catch (BadImageFormatException)
            {
                // ignore attempts to load non-managed DLLs
                return null;
            }
            catch (Exception ex)
            {
                var msg = string.Format("Error loading HostedAdapter from {0}: {1}", Path.GetFileName(filePath), ex.Message);
                RaiseStatusUpdateRequested("[local]", msg);
                Debug.WriteLine(msg);
                if (Debugger.IsAttached) Debugger.Break();
                return null;
                // TODO:
            }
        }

        public void LoadInstance(string adapterName, string instanceName)
        {
            if (m_loadedDlls.Keys.Contains(adapterName))
            {
                var adapter = LoadInstance(m_loadedDlls[adapterName], instanceName, "");
                
                adapter.AgentInitialized();
                
                adapter.StartPublishing();

                adapter.PublishDefaultData();
            }
        }

        public void RemoveInstance(string adapterName, string instanceName)
        {
            IHostedAdapter removalInstance = m_adapters.Find(t => t.ID == instanceName);

            if (removalInstance == null) return;

            RemoveAdapter(removalInstance);
        }

        public void RemoveAdapter(string adapterName)
        {
            //if (m_loadedDlls.Keys.Contains(adapterName))
            //{
            //    var adapter = LoadInstance(m_loadedDlls[adapterName], instanceName, "");
            //    adapter.AgentInitialized();
            //    adapter.PublishDefaultData();
            //}
        }

        private void RaiseStatusUpdateRequested(string source, string description)
        {
            StatusUpdateRequested.Fire(this, new GenericEventArgs<StatusSummary>(new StatusSummary
                {
                    Source = source,
                    Description = description,
                    TimeStamp = DateTime.Now
                }));
        }

        void m_watcher_NewDLLDetected(object sender, GenericEventArgs<string> e)
        {
            LoadDLL(e.Value);
        }

        void ProviderAdapterConfigurationChanged(object sender, EventArgs e)
        {
            OnConfigurationUpdate();
        }

        void Adapters_Cleared(object sender, EventArgs e)
        {
//            OnConfigurationUpdate();
        }

        public void AddAdapter(IHostedAdapter adapter)
        {
            InitializeAdapter(adapter);

            lock (m_adapters)
            {
                m_adapters.Add(adapter);
            }
        }

        public void RemoveAdapter(IHostedAdapter adapter)
        {
            Provider.Agent.Adapters.Remove(adapter.Device.ID);

            lock (m_adapters)
            {
                m_adapters.Remove(adapter);
            }

            adapter.Dispose();

            OnConfigurationUpdate();
        }

        public IAgentInterface CreateAgentInterface()
        {
            if (m_localInterface == null)
            {
                if (Provider == null)
                {
                    Debug.WriteLine("HostedAdapterService.CreateAgentInterface: Provider == null");
                    return null;
                }
                m_localInterface = new AgentInterface(Provider.Agent, this);
            }
            return m_localInterface;
        }

        private void InitializeAdapter(IHostedAdapter adapter)
        {
            try
            {
                adapter.BeforeLoad();

                adapter.CreateDeviceAndComponents();
                
                adapter.AgentInterface = CreateAgentInterface();

                var device = adapter.Device;
                var ha = new Adapter(device);

                if (Provider == null)
                {
                    Debug.WriteLine("HostedAdapterService.CreateAgentInterface: Provider == null");
                }
                else
                {
                    Provider.Agent.Adapters.Add(ha);
                }

                adapter.LoadPropertiesAndMethods();

                adapter.AfterLoad();

                //adapter.StartPublishing();
            }
            catch (Exception ex)
            {
                adapter.OnError(ex);
            }
        }

        public void StartAdapterPublishing()
        {
            foreach (var adapter in m_adapters)
            {
                adapter.PublishDefaultData();

                adapter.StartPublishing();
            }
        }

        public void OnConfigurationUpdate()
        {
            // if the config changes (i.e. from a REST call) all devices get cleared.  We need to go in and re-add
            // any devices added via IHostedAdapters
            lock (m_adapters)
            {
                foreach (var adapter in m_adapters)
                {
                    try
                    {
                        var existing = Provider.Agent.Adapters.FirstOrDefault(a => a.Device.ID == adapter.Device.ID);
                        if (existing != null) continue;

                        var device = adapter.Device;
                        var ha = new Adapter(device);
                        Provider.Agent.Adapters.Add(ha);

                        adapter.OnConfigurationChange();
                    }
                    catch (Exception ex)
                    {
                        adapter.OnError(ex);
                    }
                }
            }
        }

        [EventSubscription(EventNames.ShutdownRequested, ThreadOption.Caller)]
        public void OnShutdownRequested(object sender, EventArgs e)
        {
            Shutdown();
        }
    }
}
